import { PokerHand } from "../constant/PokerHand";
import { PokerPoints } from "../constant/PokerPoints";
import { PokerSuits } from "../constant/PokerSuits";
import { IPokerVo, Poker } from "../data/Poker";

/**
 * 扑克牌工具类：判断牌组中的每张牌是否唯一、符合斗地主规则
 */
export default class PokerUtil {
    /**
     * 判断牌组中的每张牌是否唯一(花色+牌值)
     * @param cards 
     */
    public static isOnly(cards: Array<IPokerVo>): boolean {
        let set: Set<string> = new Set()
        for (let i = 0; i < cards.length; i++) {
            const cardStr = JSON.stringify(cards[i])
            if (set.has(cardStr)) {
                return false
            }
            set.add(cardStr)
        }

        return true
    }

    /**
     * c2是否存在c1中（允许多次包含——即cards2存在两个相同的值，而cards1只有一个，这也算匹配成功。如果要判断一组排是否某人的手牌需要要调用一次isOnly()）
     * @param c1 
     * @param c2 
     * @returns 
     */
    public static contain(cards1: Array<Poker>, cards2: Array<Poker>): boolean {
        if (cards1.length < cards2.length) {
            return false
        }

        let cardsSet = new Set<string>()
        cards1.forEach(card => {
            cardsSet.add(JSON.stringify(card))
        })

        for (let i = 0; i < cards2.length; i++) {
            const card = cards2[i]
            if (!cardsSet.has(JSON.stringify(card))) {
                return false
            }
        }

        return true
    }

    /**
     * 检测一副牌的牌型（hand会将cards牌组排序）
     * @param cards 
     */
    public static hand(cards: Array<IPokerVo>): PokerHand {
        // 所有牌是否唯一（安全校验）
        if (!this.isOnly(cards)) {
            return PokerHand.NONE
        }

        // 排序
        cards = this.sort(cards)

        const count = cards.length
        // 按牌数划分检测逻辑
        if (count === 1) {
            // 单张
            return PokerHand.T_A
        } else if (count === 2) {
            if (this.isSame(cards)) {
                // 对子
                return PokerHand.T_AA
            } else if (this.isRocket(cards)) {
                // 王炸（火箭）
                return PokerHand.T_ROCKET
            }
        } else if (count === 3) {
            if (this.isSame(cards)) {
                // 三张
                return PokerHand.T_AAA
            }
        } else if (count === 4) {
            if (this.isSame(cards)) {
                // 炸弹
                return PokerHand.T_AAAA
            }
            else if (this.isAAAB(cards)) {
                // 三带一
                return PokerHand.T_AAA_B
            }
        } else if (count >= 5) {
            if (this.isABCDE(cards)) {
                // 顺子
                return PokerHand.T_ABCDE
            } else if (this.isAAABB(cards)) {
                // 三带二，三带两对 AAA BC / AAA BBCC
                return PokerHand.T_AAA_BB
            } else if (this.isAABBCC(cards)) {
                // 双顺子 AABBCC
                return PokerHand.T_AABBCC
            } else if (this.isAAABBB(cards)) {
                // 三顺子(飞机) AAABBB
                return PokerHand.T_AAABBB
            }
            else if (this.isAAABBBCD(cards)) {
                // 飞机带翅膀 AAABBB CD / AAABBB CCDD
                return PokerHand.T_AAABBB_CD
            }
        }

        return PokerHand.NONE
    }

    /**
     * 判断cards2能否压cards1
     * @param cards1 
     * @param cards2
     */
    public static legal(cards1: Array<IPokerVo>, cards2: Array<IPokerVo>): boolean {
        // 判断牌组牌型，并且对牌组进行排序
        const hand1 = this.hand(cards1)
        const hand2 = this.hand(cards2)

        if (hand1 == PokerHand.T_ROCKET || hand2 == PokerHand.NONE) {
            // cards1是王炸，直接false
            // cards2没有牌型，直接false
            return false
        }

        //  && hand2 != PokerHand.NONE
        if (hand1 == PokerHand.NONE) {
            // cards1没有牌型，cards2有牌型（上一个if已确定） 直接true
            return true
        }

        if (hand1 === hand2 && cards1.length === cards2.length) {
            // 牌型相等、并且牌数相同
            switch (hand1) {
                // 单张
                case PokerHand.T_A:
                // 对子
                case PokerHand.T_AA:
                // 三张
                case PokerHand.T_AAA:
                // 炸弹
                case PokerHand.T_AAAA:
                // 顺子
                case PokerHand.T_ABCDE:
                // 双顺子
                case PokerHand.T_AABBCC:
                // 三顺子
                case PokerHand.T_AAABBB:
                    // 比较最后一张点数权重的大小
                    if (this.compareLastByPointWeight(cards1, cards2) < 0) {
                        return true
                    }
                    break;
                // 飞机带翅膀
                case PokerHand.T_AAABBB_CD:
                    const cards1By3 = this.getThreeSomeArr(cards1)
                    const cards2By3 = this.getThreeSomeArr(cards2)
                    // 比较最后一张点数权重的大小
                    if (this.compareLastByPointWeight(cards1By3, cards2By3) < 0) {
                        return true
                    }
                    break;
                // 三带一
                case PokerHand.T_AAA_B:
                // 三带二、三带两对
                case PokerHand.T_AAA_BB:
                    const card1 = this.getThreeSome(cards1)
                    const card2 = this.getThreeSome(cards2)
                    if (!card1 || !card2) {
                        throw new Error('no three are the same')
                    }
                    if (this.compareLastByPointWeight([card1], [card2]) < 0) {
                        return true
                    }
                    break;
            }

        } else if (hand1 !== hand2) {
            // 牌型不相等；牌数可相等，也可不相等
            // cards2是王炸、炸弹，可以直接压
            if (hand2 === PokerHand.T_ROCKET || hand2 === PokerHand.T_AAAA) {
                return true
            }
        }

        // 牌型相等，但是牌数不相等，直接false
        return false
    }

    /**
     * 对一副牌从大到小排序
     * @param cards 
     * @returns 
     */
    public static sort(cards: Array<IPokerVo>) {
        return cards.sort((c1: IPokerVo, c2: IPokerVo) => {
            const poker1 = new Poker(c1)
            const poker2 = new Poker(c2)
            return poker1.compare(poker2)
        }).reverse()
    }

    /**
     * 牌组的点数是否都相等
     * @param cards 
     */
    public static isSame(cards: Array<IPokerVo>): boolean {
        let preCard = cards[0]
        for (let i = 1; i < cards.length; i++) {
            const currCard = cards[i]
            // 不一样 return false
            if (preCard.point != currCard.point) {
                return false
            }
        }
        return true
    }

    /**
     * 是否是王炸（火箭）
     * @param cards 
     * @returns 
     */
    public static isRocket(cards: Array<IPokerVo>): boolean {
        if (cards.length != 2) {
            return false
        }

        if (cards[0].suit !== PokerSuits.NONE || cards[1].suit !== PokerSuits.NONE) {
            return false
        }

        // 要么大王小王，要么小王大王
        if ((cards[0].point == PokerPoints.P_RJ && cards[1].point == PokerPoints.P_BJ) ||
            (cards[0].point == PokerPoints.P_BJ && cards[1].point == PokerPoints.P_RJ)) {
            return true
        }

        return false
    }

    /**
     * 是否是三带一（牌组必须排序后使用）
     */
    public static isAAAB(cards: Array<IPokerVo>): boolean {
        if (cards.length != 4) {
            return false
        }
        // 获取前3张
        let tempArr = cards.slice(0, cards.length - 1)
        if (this.isSame(tempArr)) {
            return true
        }

        // 获取后3张
        tempArr = cards.slice(1, cards.length)
        if (this.isSame(tempArr)) {
            return true
        }

        return false
    }

    /**
     * 是否是顺子（牌组必须大到小排序）
     * @param cards 
     */
    public static isABCDE(cards: Array<IPokerVo>): boolean {
        if (cards.length < 5) {
            return false
        }

        // 只能是3~A 的点数
        const pokerFirst = new Poker(cards[0])
        const pokerLast = new Poker(cards[cards.length - 1])
        if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
            3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
            return false
        }

        // 大到小的情况
        let i = 0
        for (i = 1; i < cards.length; i++) {
            const c1 = new Poker(cards[i - 1])
            const c2 = new Poker(cards[i])
            // 上一张牌不比当前的牌大1
            if (c1.pointWeight - c2.pointWeight !== 1) {
                return false
            }
        }

        return true
    }

    /**
     * 判断是否是三带一对、三带两对
     */
    public static isAAABB(cards: Array<IPokerVo>): boolean {
        // AAA BC、AAA BBCC
        if (cards.length != 5 && cards.length != 7) {
            return false
        }

        // 统计牌数
        let map: Map<PokerPoints, number> = new Map() //记牌器
        cards.forEach(card => {
            if (map.has(card.point)) {
                // count + 1
                const count = map.get(card.point)!
                map.set(card.point, count + 1)
            } else {
                // init
                map.set(card.point, 1)
            }
        })

        if (cards.length == 5) {
            // 三带一对判断
            let flags = [0, 0]  // 下标0:表示3对出现的次数，下标1:表示2对出现的次数
            for (let value of map.values()) {
                if (value == 3) {
                    flags[0] += 1
                } else if (value == 2) {
                    flags[1] += 1
                }
            }
            return flags[0] == 1 && flags[1] == 1

        } else if (cards.length == 7) {
            // 三带两对判断
            let flags = [0, 0] // 下标0:表示3对出现的次数，下标1:表示2对出现的次数
            for (let value of map.values()) {
                if (value == 3) {
                    flags[0] += 1
                } else if (value == 2) {
                    flags[1] += 1
                }
            }

            return flags[0] == 1 && flags[1] == 2
        }

        return false
    }

    /**
     * 双顺子、连对（必须大到小排序）
     * @param cards 
     * @returns 
     */
    public static isAABBCC(cards: Array<IPokerVo>): boolean {
        // AABBCC、AABBCCDD
        if (cards.length < 6 || cards.length % 2 !== 0) {
            // 小于6 或者 不是2的倍数，直接pass
            return false
        }

        // 只能是3~A 的点数
        const pokerFirst = new Poker(cards[0])
        const pokerLast = new Poker(cards[cards.length - 1])
        if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
            3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
            return false
        }

        //大到小
        // 0 1 2 3 4 5
        // 01 23 45
        // 6-2 = 4
        for (let i = 0; i < cards.length; i += 2) {
            const c1 = new Poker(cards[i])
            const c2 = new Poker(cards[i + 1])
            if (c1.point != c2.point) {
                // 不是成对关系，直接false
                return false
            }

            // 最后一组不需要和下一组比较差值
            if (i >= cards.length - 2) {
                break
            }

            // 和下一组比较差值
            const c3 = new Poker(cards[i + 2])
            if (c1.pointWeight - c3.pointWeight != 1) {
                return false
            }
        }

        return true
    }

    /**
     * 三顺子、飞机（必须大到小排序）
     * @param cards 
     * @returns 
     */
    public static isAAABBB(cards: Array<IPokerVo>): boolean {
        // AAABBB、AAABBBCCC、AAABBBCCCDDD

        // 小于6 或 不是3的倍数 直接pass
        if (cards.length < 6 || cards.length % 3 !== 0) {
            return false
        }

        // 只能是3~A 的点数
        const pokerFirst = new Poker(cards[0])
        const pokerLast = new Poker(cards[cards.length - 1])
        if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
            3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
            return false
        }

        // 三三匹配
        // 大到小情况
        for (let i = 0; i < cards.length; i += 3) {
            const c1 = cards[i]
            const c2 = cards[i + 1];
            const c3 = cards[i + 2];
            const cNext = cards[i + 3];

            if (c1.point !== c2.point || c2.point !== c3.point) {
                return false
            }

            // 最后一组不需要和下一组比较差值
            if (i >= cards.length - 3) {
                break
            }

            if (c1.point - cNext.point != 1) {
                return false
            }
        }

        return true
    }

    /**
     * 飞机带翅膀（必须排序）
     * @param cards 
     * @returns 
     */
    public static isAAABBBCD(cards: Array<IPokerVo>): boolean {
        // 组合方式：AAABBB-CD、AAABBB-CCDD、AAABBBCCC-DEF、AAABBBCCC-DDEEFF
        // 排序情况：AAABBB-CD、CD-AAABBB、C-AAABBB-D
        if (cards.length < 8) {
            return false
        }

        // map记牌 key = 牌值 value = array
        let cardMap: Map<PokerPoints, Array<IPokerVo>> = new Map()
        for (const card of cards) {
            if (cardMap.has(card.point)) {
                cardMap.get(card.point)?.push(card)
            } else {
                cardMap.set(card.point, [card])
            }
        }

        const cardsByCount3: Array<IPokerVo> = []
        let count3 = 0
        // 获取为3张的牌
        for (const cards of cardMap.values()) {
            if (cards.length == 3) {
                cardsByCount3.push(...cards)
                cardMap.delete(cards[0].point)
                count3++
            }
        }

        // console.log('cardMap:', cardMap)
        // console.log('cardByCount3:', cardByCount3)

        // 判断这三张是否是飞机
        if (this.isAAABBB(cardsByCount3) == false) {
            // console.log('not isAAABBB')
            return false
        }

        // 判断剩余的牌
        let cardCount = 0     // 剩下牌，第一组的数量
        let groupCount = 0    // 剩下的牌，还剩多少组
        for (const cards of cardMap.values()) {
            if (cardCount == 0) {
                cardCount = cards.length
            }

            // console.log('cards:', cards)
            // console.log('cardCount:', cardCount, 'card.length:', cards.length)
            if ((cardCount != 1 && cardCount != 2) || cardCount != cards.length) {
                return false
            }

            groupCount++
        }

        return count3 === groupCount
    }

    /**
     * 比较最后一张（比较点数的权重）
     */
    private static compareLastByPointWeight(cards1: Array<IPokerVo>, cards2: Array<IPokerVo>): number {
        const poker1 = new Poker(cards1[cards1.length - 1])
        const poker2 = new Poker(cards2[cards2.length - 1])
        return poker1.compareByPointWeight(poker2)
    }

    /**
     * 如果有三张相等，返回其中一张（牌组必须有序）
     * @param cards 
     * @returns 
     */
    private static getThreeSome(cards: Array<IPokerVo>): IPokerVo | undefined {
        for (let i = 0; i < cards.length - 2; i++) {
            const c1 = cards[i]
            const c2 = cards[i + 1]
            const c3 = cards[i + 2]
            if (c1.point === c2.point && c2.point === c3.point) {
                return c1
            }
        }
    }

    /**
     * 获取所有3张点数都相同牌（必须排序）
     * @param cards 
     */
    private static getThreeSomeArr(cards: Array<IPokerVo>): Array<IPokerVo> {
        // map记牌 key = 牌值 value = array
        let cardMap: Map<PokerPoints, Array<IPokerVo>> = new Map()
        for (const card of cards) {
            if (cardMap.has(card.point)) {
                cardMap.get(card.point)?.push(card)
            } else {
                cardMap.set(card.point, [card])
            }
        }

        const cardsByCount3: Array<IPokerVo> = []
        // 获取为3张的牌
        for (const cards of cardMap.values()) {
            if (cards.length == 3) {
                cardsByCount3.push(...cards)
                cardMap.delete(cards[0].point)
            }
        }

        return cardsByCount3
    }
}